/**
 * Octopus, an XMMS2 client.
 * Copyright (C) 2014 Octopus Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace OctopusMx {
	public class MxTracklist : Mx.BoxLayout {
		public signal void remove (MxFrameTrack track);
		private Gee.List<MxFrameTrack> list;
		private Gee.List<MxFrameTrack> selected;
		private Clutter.DragAction drag_action;
		//use get-, set-methods for static variables in js code
		private static Clutter.Stage? stage = null;
		internal static MxTracklist? drag_actor = null;
		internal static DragAndDropStatus drag_and_drop_status = DragAndDropStatus.NONE;

		public enum DragAndDropStatus {
			NONE,
			DRAG_MOVE,
			DRAG_COPY,
			DROP_SUCCESS
		}

		public MxTracklist () {
			Object ();
		}

		construct {
			list = new Gee.ArrayList<MxFrameTrack> ();
			selected = new Gee.ArrayList<MxFrameTrack> ();
		}

		public static MxTracklist? get_drag_actor () {
			return drag_actor;
		}

		public static DragAndDropStatus get_drag_and_drop_status () {
			return drag_and_drop_status;
		}

		public static void set_drag_and_drop_status (DragAndDropStatus status) {
			drag_and_drop_status = status;
		}

		public static void set_stage (Clutter.Stage s) {
			stage = s;
		}

		public void add_track (MxFrameTrack track) {
			base.add_child (track);
			list.add(track);
		}

		public void add_track_at_pos (MxFrameTrack track, int pos) {
			base.insert_child_at_index (track, pos);
			list.insert (pos, track);
		}

		public void remove_track (MxFrameTrack track) {
			list.remove (track);
			selected.remove (track);
			base.remove_child (track);
		}

		public void clear_tracklist () {
			vertical_adjustment.value = 0;
			list.clear ();
			selected.clear ();
			base.remove_all_children ();
		}

		public int get_list_size () {
			return list.size;
		}

		public MxFrameTrack get_track (int i) {
			return list[i];
		}

		public int get_selected_size () {
			return selected.size;
		}

		public MxFrameTrack get_selected (int i) {
			return selected[i];
		}

		public int index_of (MxFrameTrack track) {
			return list.index_of (track);
		}

/**********************************************************************/
/* select tracks                                                      */
/**********************************************************************/
		public void unselect_tracklist () {
			foreach (MxFrameTrack track in list) {
				track.selected = false;
			}
			selected.clear ();
		}

		public void toggle_selected (MxFrameTrack track) {
			track.selected = !track.selected;
			if (track.selected) {
				if (!selected.contains (track)) selected.add (track);
			} else {
				if (selected.contains (track)) selected.remove (track);
			}
		}

		public void set_selected (MxFrameTrack track) {
			track.selected = true;
			if (!selected.contains (track)) selected.add (track);
		}

		public void multi_select (MxFrameTrack track) {
			//select all tracks between lastSelectedIndex and track
			if (selected.size == 0) { //nothing has been selected yet
				set_selected (track);
			} else {
				int lastSelectedIndex = list.index_of (selected [selected.size - 1]);
				if (list.index_of (track) > lastSelectedIndex) {
					for (int i=lastSelectedIndex; i <= list.index_of (track); i++) {
						list[i].selected = true;
						if (!selected.contains (list[i])) selected.add (list[i]);
					}
				} else if (list.index_of (track) < lastSelectedIndex) {
					for (int i=lastSelectedIndex; i >= list.index_of (track); i--) {
						list[i].selected = true;
						if (!selected.contains (list[i])) selected.add (list[i]);
					}
				}
			}
		}

		public void add_selected_next () {
			if (selected.size != 0) {
				var lastSelected = selected[selected.size - 1];
				int nextTrackIndex = (list.index_of (lastSelected) + 1);
				if (nextTrackIndex < list.size) {
					if (selected.contains (list[nextTrackIndex])) {
						selected.remove (list[nextTrackIndex]);
					}
					set_selected (list[nextTrackIndex]);
				}
			}
		}

		public void add_selected_prev () {
			if (selected.size != 0) {
				var lastSelected = selected[selected.size - 1];
				int prevTrackIndex = (list.index_of (lastSelected) - 1);
				if (prevTrackIndex >= 0) {
					if (selected.contains (list[prevTrackIndex])) {
						selected.remove (list[prevTrackIndex]);
					}
					set_selected (list[prevTrackIndex]);
				}
			}
		}

		public void remove_selected () {
			//remove_track removes actors from list and Mx container (visual changes)
			//here we emit a remove signal for selected tracks,
			//we could as well just call xmms.playlist_remove_entry but
			//want to be more flexible and let signal catcher do it

			//xmms.playlist_remove_entry uses position index parameter,
			//we delete tracks "bottom up" to not run into
			//conflicts or any sort of race conditions
			//dont use selected list cause this list contains tracks
			//in order they been selected
			for (int i=list.size-1; i>=0; i--) {
					if (list[i].selected) remove(list[i]);
			}
		}

/**********************************************************************/
/* drag and drop                                                      */
/**********************************************************************/
		//drag is a list operation (all selected tracks in list)
		public void enable_drag_action () {
			drag_action = new Clutter.DragAction ();
			drag_action.x_drag_threshold = 5;
			drag_action.y_drag_threshold = 5;
			drag_action.drag_begin.connect ((/*Clutter.DragAction*/   action,
			                                 /*Clutter.Actor*/        actor,
			                                 /*gfloat*/               event_x,
			                                 /*gfloat*/               event_y,
			                                 /*Clutter.ModifierType*/ modifiers) => {
				if ((modifiers & Clutter.ModifierType.CONTROL_MASK) == Clutter.ModifierType.CONTROL_MASK) {
					//drag insert
					MxTracklist.drag_and_drop_status = DragAndDropStatus.DRAG_COPY;
				} else {
					//drag copy
					MxTracklist.drag_and_drop_status = DragAndDropStatus.DRAG_MOVE;
				}

				//set "active" tracks selected
				foreach (MxFrameTrack track in list) {
					if (track.is_active ()) {
						track.remove_active ();
						track.remove_hover ();
						track.selected = true;
						if (!selected.contains (track)) selected.add (track);
					}
				}
				//create drag handle
				var drag_handle = new Mx.BoxLayout ();
				drag_handle.orientation = Mx.Orientation.VERTICAL;
				drag_handle.transitions_completed.connect ((/*Mx.BoxLayout*/ actor) => {
					actor.remove_all_children ();
					actor.destroy ();
				});
				float x_pos, y_pos;
				actor.get_position (out x_pos, out y_pos);
				drag_handle.set_position (event_x - x_pos, event_y - y_pos);
				foreach (MxFrameTrack track in selected) {
					drag_handle.add_child (track.get_drag_handle ());
				}
				if (MxTracklist.stage != null) {
					MxTracklist.stage.add_child (drag_handle);
				} else {
					GLib.warning ("warning: OctopusMx.MxTracklist.stage was not set.");
				}
				action.set_drag_handle (drag_handle);
				drag_actor = this;
			});
			//drag_action.drag_motion.connect ((/*Clutter.DragAction*/ action,
			//                                  /*Clutter.Actor*/      actor,
			//                                  /*gfloat*/             delta_x,
			//                                  /*gfloat*/             delta_y) => {
			//	GLib.debug ("drag-motion");
			//});
			drag_action.drag_end.connect ((/*Clutter.DragAction*/   action,
			                               /*Clutter.Actor*/        actor,
			                               /*gfloat*/               event_x,
			                               /*gfloat*/               event_y,
			                               /*Clutter.ModifierType*/ modifiers) => {
				var drag_handle = action.get_drag_handle ();
				drag_handle.save_easing_state ();
				if (MxTracklist.drag_and_drop_status == DragAndDropStatus.DROP_SUCCESS) {
					drag_handle.set_easing_mode (Clutter.AnimationMode.LINEAR);
					drag_handle.set_opacity (0);
				} else {
					float x_pos, y_pos;
					actor.get_transformed_position (out x_pos, out y_pos);
					drag_handle.set_easing_mode (Clutter.AnimationMode.EASE_OUT_BOUNCE);
					drag_handle.set_position (x_pos, y_pos);
					drag_handle.set_opacity (0);
				}
				drag_handle.restore_easing_state ();
				MxTracklist.drag_and_drop_status = DragAndDropStatus.NONE;
			});
			reactive = true;
			add_action (drag_action);
		}
	}
}
